package com.gitee.l0km.codegen.wsdl;

import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.wsdl.Definition;
import javax.xml.namespace.QName;

import org.apache.axis2.description.AxisService;
import org.apache.axis2.description.java2wsdl.DefaultNamespaceGenerator;
import org.apache.axis2.description.java2wsdl.DefaultSchemaGenerator;
import org.apache.axis2.description.java2wsdl.Java2WSDLConstants;
import org.apache.axis2.description.java2wsdl.NamespaceGenerator;
import org.apache.axis2.description.java2wsdl.TypeTable;
import org.apache.axis2.description.java2wsdl.bytecode.MethodTable;
import org.apache.ws.commons.schema.XmlSchema;
import org.apache.ws.commons.schema.XmlSchemaComplexContentExtension;
import org.apache.ws.commons.schema.XmlSchemaComplexType;
import org.apache.ws.commons.schema.XmlSchemaContent;
import org.apache.ws.commons.schema.XmlSchemaContentModel;
import org.apache.ws.commons.schema.XmlSchemaContentType;
import org.apache.ws.commons.schema.XmlSchemaElement;
import org.apache.ws.commons.schema.XmlSchemaObject;
import org.apache.ws.commons.schema.XmlSchemaObjectCollection;
import org.apache.ws.commons.schema.XmlSchemaParticle;
import org.apache.ws.commons.schema.XmlSchemaSequence;
import org.apache.ws.commons.schema.XmlSchemaType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.gitee.l0km.codegen.base.CodeGenUtils;
import com.gitee.l0km.codegen.base.Method;
import com.gitee.l0km.com4j.base.Assert;
import com.gitee.l0km.com4j.base.Judge;

public class WebServiceInfo {
	private static final Logger logger = LoggerFactory.getLogger(WebServiceInfo.class);
	private static final NamespaceGenerator NAMESPACEGENERATOR = new DefaultNamespaceGenerator();
	/**
	 * 暂时定义等全部调试通过并修改过build-common-axis2-service.xml后，这个参数应该拿掉
	 */
	private static final String SCHEMATARGETNAMESPACEPREFIX = null;// "xsd";
	private final Class<?> serviceClass;
	private final Definition definition;
	private final MethodTable methodTable;
	private final String serviceName;
	private final AxisService axisService;
	private final Map<String, Method> ports = new HashMap<String, Method>();

	private Collection<XmlSchema> schema;
	private final TypeTable typeTable;
	/**
	 * @param wsdlURI
	 * @param serviceClass
	 * @param serviceName
	 */
	public WebServiceInfo(final URI wsdlURI, final Class<?> serviceClass, String serviceName) {
		Assert.notNull(serviceClass, "serviceClass");
		Assert.notNull(wsdlURI, "wsdlURI");
		try {
			this.serviceClass = serviceClass;
			this.methodTable = new MethodTable(serviceClass);
			this.definition = WSDLUtilits.loadWSDL(wsdlURI);
			List<AxisService> allAxisServices = WSDLUtilits.createAxisService(this.definition);

			this.serviceName = Judge.isEmpty(serviceName) ? serviceClass.getSimpleName() : serviceName;
			this.axisService = getAxisService(this.serviceName, allAxisServices);
			String schematargetNamespace = NAMESPACEGENERATOR.namespaceFromPackageName(
					serviceClass.getPackage().getName()).toString();
			DefaultSchemaGenerator schemaGen = new DefaultSchemaGenerator(serviceClass.getClassLoader(),
					serviceClass.getName(), schematargetNamespace, SCHEMATARGETNAMESPACEPREFIX, axisService);
			schema = schemaGen.generateSchema();
			typeTable=schemaGen.getTypeTable();
			axisService.setTypeTable(typeTable);
			fillMethodMap(schemaGen);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	private final AxisService getAxisService(final String serviceName, List<AxisService> allAxisServices) {
		for (AxisService service : allAxisServices) {
			if (service.getName().equals(serviceName))
				return service;
		}
		return null;
	}

	private void fillMethodMap(DefaultSchemaGenerator schemaGen) {
		java.lang.reflect.Method[] methods = schemaGen.getMethods();
		for (java.lang.reflect.Method method : methods) {
			this.ports.put(method.getName(), new Method(method, methodTable.getParameterNames(method.getName())));
		}
	}

	/**
	 * @return ports
	 */
	public Map<String, Method> getPorts() {
		return ports;
	}

	/**
	 * @return serviceClass
	 */
	public Class<?> getServiceClass() {
		return serviceClass;
	}

	/**
	 * @return definition
	 */
	public Definition getDefinition() {
		return definition;
	}

	/**
	 * @return schema
	 */
	public Collection<XmlSchema> getSchema() {
		return schema;
	}

	/**
	 * @return axisService
	 */
	public AxisService getAxisService() {
		return axisService;
	}

	public XmlSchema getSchema(String namespaceURI) {
		for (XmlSchema x : schema) {
			if (x.getTargetNamespace().equals(namespaceURI))
				return x;
		}
		return null;
	}

	private XmlSchemaParticle getParticle(XmlSchemaComplexType complexType) {
		XmlSchemaContentModel model = complexType.getContentModel();
		if (model == null) {
			return complexType.getParticle();
		}
		XmlSchemaContent content = model.getContent();
		if (!(content instanceof XmlSchemaComplexContentExtension)) {
			return null;
		}
		return ((XmlSchemaComplexContentExtension) content).getParticle();
	}

	public XmlSchemaComplexType getXmlSchemaComplexType(QName typeName) {
		XmlSchema schema = getSchema(typeName.getNamespaceURI());
		Assert.notNull(schema, "schema");
		XmlSchemaType type = schema.getTypeByName(typeName);
		if (type == null) {
			XmlSchemaElement element = schema.getElementByName(typeName);
			if(null!=element){
				type = element.getSchemaType();	
			}else
				return null;			
		}
		if (type instanceof XmlSchemaComplexType) {
			return (XmlSchemaComplexType) type;
		} else
			throw new UnexpectedXmlSchemObjectException(type);
	}

	public ArrayList<XmlSchemaElement> getFieldElements(QName typeName) {
		final ArrayList<XmlSchemaElement> paramSeq = new ArrayList<XmlSchemaElement>();
		if (null != typeName) {
			XmlSchemaComplexType complexType = getXmlSchemaComplexType(typeName);
			paramSeq.addAll(getFieldElements(complexType.getBaseSchemaTypeName()));
			XmlSchemaParticle particle = getParticle(complexType);
			if (particle instanceof XmlSchemaSequence) {
				XmlSchemaObjectCollection colllections = ((XmlSchemaSequence) particle).getItems();
				for (int i = 0; i < colllections.getCount(); i++) {
					XmlSchemaObject item = colllections.getItem(i);
					if (item instanceof XmlSchemaElement) {
						paramSeq.add((XmlSchemaElement) item);
					} else
						throw new UnexpectedXmlSchemObjectException(item);
				}
			} else if (particle instanceof XmlSchemaElement) {
				paramSeq.add((XmlSchemaElement) particle);
			} else
				throw new UnexpectedXmlSchemObjectException(particle);

		}
		return paramSeq;
	}

	public String[] getFieldSequence(QName typeName) {
		ArrayList<XmlSchemaElement> list = getFieldElements(typeName);
		String[] seq = new String[list.size()];
		for (int i = 0; i < seq.length; i++) {
			seq[i] = list.get(i).getName();
		}
		return seq;
	}

	public Map<QName, String> getQNameToClassNameMap() {
		return getQNameToClassNameMap(this.axisService.getTypeTable());
	}

	public Map<QName, Class<?>> getQNameToClassMap() {
		Map<QName, String> map = getQNameToClassNameMap();
		HashMap<QName, Class<?>> qNameToClassMap = new HashMap<QName, Class<?>>(map.size());
		try {
			for (Iterator<Entry<QName, String>> it = map.entrySet().iterator(); it.hasNext();) {
				Entry<QName, String> entry = it.next();
				qNameToClassMap.put(entry.getKey(), CodeGenUtils.forName(entry.getValue()));
			}
		} catch (ClassNotFoundException e) {
			throw new RuntimeException(e);
		} finally {
		}
		return qNameToClassMap;
	}

	private Map<QName, String> getQNameToClassNameMap(final TypeTable typeTable) {
		Map<QName, String> qNameToClassMap = new HashMap<QName, String>();
		for (@SuppressWarnings("unchecked")
		Iterator<QName> it = typeTable.getComplexSchemaMap().values().iterator(); it.hasNext();) {
			QName qn = it.next();
			String className = typeTable.getClassNameForQName(qn);
			if (className != null)
				qNameToClassMap.put(qn, className);
		}
		return qNameToClassMap;
	}

	public String getPrefix(QName name) {
		String prefix = name.getPrefix();
		if (Judge.isEmpty(prefix)) {
			for (Object e : axisService.getNamespaceMap().entrySet()) {
				Entry<String, String> entry = (Entry<String, String>) e;
				if (entry.getValue().equals(name.getNamespaceURI()))
					return entry.getKey();
			}
		}
		return prefix;
	}

	/**
	 * @return typeTable
	 */
	public TypeTable getTypeTable() {
		return typeTable;
	}

	/**
	 * @return serviceName
	 */
	public String getServiceName() {
		return serviceName;
	}
	
}
